/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2005, 2009 INRIA
 * Copyright (c) 2009 MIRKO BANCHI
 * Copyright (c) 2014, INDIAN INSTITUTE OF TECHNOLOGY, MADRAS
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Author: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
 * Author: Mirko Banchi <mk.banchi@gmail.com>
 * Author:Poornima Kalyan Sundaram <poornimaks93@gmail.com>
 */
#include "ns3/ipv4-header.h"
#include "ns3/simulator.h"
#include "ns3/packet.h"
#include "ns3/double.h"
#include "ns3/pointer.h"
#include "ns3/uinteger.h"
#include "ns3/log.h"
#include "ns3/random-variable-stream.h"
#include "wifi-mac-queue.h"
#include "qos-blocked-destinations.h"
#include "ns3/ipv4-address.h"
#include "ns3/string.h"
#include "ns3/onoff-application.h"
#include<string>


namespace ns3 {

NS_OBJECT_ENSURE_REGISTERED (WifiMacQueue);

WifiMacQueue::Item::Item (Ptr<const Packet> packet,
                          const WifiMacHeader &hdr,
                          Time tstamp)
  : packet (packet),
    hdr (hdr),
    tstamp (tstamp)
{
}

WifiMacQueue::Item::Item (Ptr<const Packet> packet,
                          const WifiMacHeader &hdr,
                          Time tstamp,double bitvector)
  : packet (packet),
    hdr (hdr),
    tstamp (tstamp),
    bitvector(bitvector)
{
}


TypeId
WifiMacQueue::GetTypeId (void)
{
  static TypeId tid = TypeId ("ns3::WifiMacQueue")
    .SetParent<Object> ()
    .AddConstructor<WifiMacQueue> ()
    .AddAttribute ("MaxPacketNumber", "If a packet arrives when there are already this number of packets, it is dropped.",
<<<<<<< HEAD
                   UintegerValue (400),
=======
                   UintegerValue (40),
>>>>>>> afa53cfcfe4d60c8f0b358961c46125f02231ff4
                   MakeUintegerAccessor (&WifiMacQueue::m_maxSize),
                   MakeUintegerChecker<uint32_t> ())
    .AddAttribute ("MaxDelay", "If a packet stays longer than this delay in the queue, it is dropped.",
                   TimeValue (Seconds (10.0)),
                   MakeTimeAccessor (&WifiMacQueue::m_maxDelay),
                   MakeTimeChecker ())
     .AddAttribute ("WeightedFairQueue", "Whether Weighted Fair Queueing is enabled or not",
                   BooleanValue (false),
                   MakeBooleanAccessor (&WifiMacQueue::m_wfqEnabled),
<<<<<<< HEAD
                   MakeBooleanChecker ())    
=======
                   MakeBooleanChecker ())
     .AddAttribute ("ap", "Whether Weighted Fair Queueing is enabled or not",
                   UintegerValue (400),
                   MakeUintegerAccessor (&WifiMacQueue::m_ap),
                   MakeUintegerChecker<uint32_t> ())
    
>>>>>>> afa53cfcfe4d60c8f0b358961c46125f02231ff4
  ;
  return tid;
}

WifiMacQueue::WifiMacQueue ()
  : m_size (0)
{
   Packet::EnablePrinting ();
   m_wfq=CreateObject<WeightedFairQueue>();
}

WifiMacQueue::~WifiMacQueue ()
{
  Flush ();
}

void
WifiMacQueue::SetMaxSize (uint32_t maxSize)
{
  m_maxSize = maxSize;
}

void
WifiMacQueue::SetMaxDelay (Time delay)
{
  m_maxDelay = delay;
  
}

uint32_t
WifiMacQueue::GetMaxSize (void) const
{
	  return m_maxSize;
}

Time
WifiMacQueue::GetMaxDelay (void) const
{
  return m_maxDelay;
}
/*************************************************************************************************/
Ipv4Address 
WifiMacQueue::GetIpAddressfromPacket(Ptr<const Packet> packet,std::string mode)
{
	
  Ipv4Address ip_address;
  Ptr <Packet> temp_packet=packet->Copy();
  PacketMetadata::ItemIterator metadataIterator = temp_packet->BeginItem();
  PacketMetadata::Item item;
  while (metadataIterator.HasNext())
  {
   item = metadataIterator.Next();
			// If we want to have an ip header
			if(item.tid.GetName() == "ns3::Ipv4Header")
			{
				//Ipv4Address 
			  Callback<ObjectBase *> constr = item.tid.GetConstructor();
			  NS_ASSERT(!constr.IsNull());
			  // Ptr<> and DynamicCast<> won't work here as all headers are from ObjectBase, not Object
			  ObjectBase *instance = constr();
			  NS_ASSERT(instance != 0);
			  Ipv4Header* ipv4Header = dynamic_cast<Ipv4Header*> (instance);
			  NS_ASSERT(ipv4Header != 0);
			  ipv4Header->Deserialize(item.current);
			  // The ipv4Header can now obtain the source of the packet
			  if(mode==std::string("destination"))
			  ip_address = ipv4Header->GetDestination();
			  else
			  ip_address = ipv4Header->GetSource();
			  // Finished, clear the ip header and go back
			  delete ipv4Header;
			  break;
			}
		  }
		  return ip_address;
}
/*************************************************************************************************/
/*************************************************************************************************/
void
WifiMacQueue::Enqueue (Ptr<const Packet> packet, const WifiMacHeader &hdr)
{
  Cleanup ();
  Time now = Simulator::Now ();
  if(m_wfqEnabled==true)
  { 
<<<<<<< HEAD
=======
	//
	/*
        
	*//*
    if(m_ap==1)
	  {
		  NS_LOG_UNCOND("Ap "<<m_size<<" "<<Simulator::Now().GetSeconds());
		}
		else if(m_ap!=400)
		{
			NS_LOG_UNCOND("Client#"<<m_ap<<" "<<m_size<<" "<<Simulator::Now().GetSeconds());
		}	
  */
>>>>>>> afa53cfcfe4d60c8f0b358961c46125f02231ff4
	  double weight=1;
    std::map<std::pair<Ipv4Address,Ipv4Address>,double>::iterator hash_it;
    Ipv4Address src_ip,dest_ip;
    if(m_wfq->m_mode==WeightedFairQueue::DESTINATION)
    {  
      dest_ip=GetIpAddressfromPacket(packet,std::string("destination"));
	  hash_it=m_wfq->m_weightsTable.find(std::make_pair('\0',dest_ip));
      if(hash_it->second!=0)
	  weight=hash_it->second;
    }
    else if(m_wfq->m_mode==WeightedFairQueue::SOURCE)
    {
      src_ip=GetIpAddressfromPacket(packet,std::string("source"));
      hash_it=m_wfq->m_weightsTable.find(std::make_pair(src_ip,'\0'));
	  if(hash_it->second!=0)
      weight=hash_it->second;
    }
   else if(m_wfq->m_mode==WeightedFairQueue::SOURCEDESTINATION)
   {
     src_ip=GetIpAddressfromPacket(packet,std::string("source"));
     dest_ip=GetIpAddressfromPacket(packet,std::string("destination"));
     hash_it=m_wfq->m_weightsTable.find(std::make_pair(src_ip,dest_ip));
	 if(hash_it->second!=0)
	 weight=hash_it->second;
   }
   PacketQueueI it;
   uint16_t endOfFlow=-1;
   double_t temp;
   int f,k=0;
   //disturb only the other flow queues
   if(m_queue.size()>0) //select the right place to insert if queue is non empty
   {
     int j=0;
     for(it=m_queue.begin();it!=m_queue.end();it++,j++)
	 {
	   if(m_wfq->m_mode==WeightedFairQueue::DESTINATION)
	   {
        if(GetIpAddressfromPacket(it->packet,std::string("destination"))==dest_ip)
		endOfFlow=j;			
	   }
	   else if(m_wfq->m_mode==WeightedFairQueue::SOURCEDESTINATION)
	   {
		 if(GetIpAddressfromPacket(it->packet,std::string("source"))==src_ip)
		 endOfFlow=j;					
	   }
	   else if(m_wfq->m_mode==WeightedFairQueue::DESTINATION)
	   {
		 if(GetIpAddressfromPacket(it->packet,std::string("source"))==src_ip&&GetIpAddressfromPacket(it->packet,std::string("destination"))==dest_ip)
		 endOfFlow=j;
	   }
	 }
     if(endOfFlow!=-1)
     {
       for(it=m_queue.begin();it!=m_queue.end();it++,k++)
	   {
		 if(endOfFlow==k)
		 {
           break;
	     }	
	  }
	}
	if(endOfFlow==-1)//no packets of the incoming flow
	{
      if(m_size==1) // just one packet 
	  {
        if(m_queue.begin()->bitvector<(packet->GetSize()/weight))
		{
          m_queue.push_back (Item (packet, hdr, now,(packet->GetSize()/weight)+m_queue.back().bitvector));
		  m_size++;
		  return;
		}
		else
		{
		  it=m_queue.begin();
		  m_queue.insert (it,Item (packet, hdr, now,(packet->GetSize()/weight)));
          m_size++;
          return;
		}
	   }
	   else 
	   {
         temp=m_queue.begin()->bitvector;
         it=m_queue.begin();
	     it++;
	   }				
     }
	else if(endOfFlow==m_maxSize-1)
	{
      return;
    }
    else if(endOfFlow==m_size-1&&m_size<m_maxSize)
    {
      m_queue.push_back (Item (packet, hdr, now,(packet->GetSize()/weight)+m_queue.back().bitvector));
	  m_size++;
      return;
    }
    else if(endOfFlow>-1&&endOfFlow<m_maxSize) 
	{	
	 // it++;					
      temp=it->bitvector;
      it++;
    }
    f=0;	
    for(;it!=m_queue.end();it++,k++)
    {
      if(it->bitvector>temp+(packet->GetSize()/weight))
      {
        f=1;
        break;
      }		
	  //temp=it->bitvector;
    }	
    if(f==1) //found a place to insert
    {					
      m_queue.insert (it,Item (packet, hdr, now,(packet->GetSize()/weight)+temp));
	  for(;it!=m_queue.end();it++)
	  {
        it->bitvector+=(packet->GetSize()/weight);			
	  }
	 // NS_LOG_UNCOND("hi");								
	}
	else if (f==0)
    {	
      m_queue.push_back (Item (packet, hdr, now,(packet->GetSize()/weight)+m_queue.back().bitvector));
	 }
   }
   else 
   {
	 temp=0;		
     m_queue.push_back (Item (packet, hdr, now,(packet->GetSize()/weight)+temp));
   }
   if (m_queue.size() > m_maxSize )
   {
     Item i=m_queue.back();
     m_queue.pop_back();
   }
   else
   m_size++;
   
   //reset bit vector of all flows so that the value of bit vector does not exceed maximum range!
   if(m_size>1)
   {
     it=m_queue.begin();
     it++;
     for(;it!=m_queue.end();it++)
     {
	    it->bitvector-=m_queue.begin()->bitvector;
     }
   }
  }
  else
<<<<<<< HEAD
  {
=======
  {/*
if(m_ap==1)
	  {
		  NS_LOG_UNCOND("Ap "<<m_size<<" "<<Simulator::Now().GetSeconds());
		}
		else if(m_ap!=400)
		{
			NS_LOG_UNCOND("Client#"<<m_ap<<" "<<m_size<<" "<<Simulator::Now().GetSeconds());
		}	
  
	
	  */
>>>>>>> afa53cfcfe4d60c8f0b358961c46125f02231ff4
    if (m_size == m_maxSize)
    {
      return;
    }
	
    m_queue.push_back (Item (packet, hdr, now));
    m_size++;
  }
 
}
/*************************************************************************************************/

void
WifiMacQueue::Cleanup (void)
{
  if (m_queue.empty ())
    {
      return;
    }

  Time now = Simulator::Now ();
  uint32_t n = 0;
  for (PacketQueueI i = m_queue.begin (); i != m_queue.end ();)
    {
      if (i->tstamp + m_maxDelay > now)
        {
          i++;
        }
      else
        {
          i = m_queue.erase (i);
          n++;
        }
    }
  m_size -= n;
}

Ptr<const Packet>
WifiMacQueue::Dequeue (WifiMacHeader *hdr)
{
  Cleanup ();
  if (!m_queue.empty ())
    {
      Item i = m_queue.front ();
      m_queue.pop_front ();
      m_size--;
      *hdr = i.hdr;
      
      return i.packet;
    }
  return 0;
}

Ptr<const Packet>
WifiMacQueue::Peek (WifiMacHeader *hdr)
{
  Cleanup ();
  if (!m_queue.empty ())
    { 
      Item i = m_queue.front ();
      *hdr = i.hdr;
      return i.packet;
    }
  return 0;
}

Ptr<const Packet>
WifiMacQueue::DequeueByTidAndAddress (WifiMacHeader *hdr, uint8_t tid,
                                      WifiMacHeader::AddressType type, Mac48Address dest)
{
  Cleanup ();
  Ptr<const Packet> packet = 0;
  if (!m_queue.empty ())
    {
      PacketQueueI it;
      for (it = m_queue.begin (); it != m_queue.end (); ++it)
        {
          if (it->hdr.IsQosData ())
            {
              if (GetAddressForPacket (type, it) == dest
                  && it->hdr.GetQosTid () == tid)
                {
                  packet = it->packet;
                  *hdr = it->hdr;
                  m_queue.erase (it);
                  m_size--;
                  break;
                }
            }
        }
    }
  return packet;
}

Ptr<const Packet>
WifiMacQueue::PeekByTidAndAddress (WifiMacHeader *hdr, uint8_t tid,
                                   WifiMacHeader::AddressType type, Mac48Address dest)
{
  Cleanup ();
  if (!m_queue.empty ())
    {
      PacketQueueI it;
      for (it = m_queue.begin (); it != m_queue.end (); ++it)
        {
          if (it->hdr.IsQosData ())
            {
              if (GetAddressForPacket (type, it) == dest
                  && it->hdr.GetQosTid () == tid)
                {
                  *hdr = it->hdr;
                  return it->packet;
                }
            }
        }
    }
  return 0;
}

bool
WifiMacQueue::IsEmpty (void)
{
  Cleanup ();
  return m_queue.empty ();
}

uint32_t
WifiMacQueue::GetSize (void)
{
  return m_size;
}

void
WifiMacQueue::Flush (void)
{
  m_queue.erase (m_queue.begin (), m_queue.end ());
  m_size = 0;
}

Mac48Address
WifiMacQueue::GetAddressForPacket (enum WifiMacHeader::AddressType type, PacketQueueI it)
{
  if (type == WifiMacHeader::ADDR1)
    {
      return it->hdr.GetAddr1 ();
    }
  if (type == WifiMacHeader::ADDR2)
    {
      return it->hdr.GetAddr2 ();
    }
  if (type == WifiMacHeader::ADDR3)
    {
      return it->hdr.GetAddr3 ();
    }
  return 0;
}

bool
WifiMacQueue::Remove (Ptr<const Packet> packet)
{
  PacketQueueI it = m_queue.begin ();
  for (; it != m_queue.end (); it++)
    {
      if (it->packet == packet)
        {
          m_queue.erase (it);
          m_size--;
          return true;
        }
    }
  return false;
}

void
WifiMacQueue::PushFront (Ptr<const Packet> packet, const WifiMacHeader &hdr)
{
  Cleanup ();
  if (m_size == m_maxSize)
    {
      return;
    }
  Time now = Simulator::Now ();
  m_queue.push_front (Item (packet, hdr, now));
  m_size++;
}

uint32_t
WifiMacQueue::GetNPacketsByTidAndAddress (uint8_t tid, WifiMacHeader::AddressType type,
                                          Mac48Address addr)
{
  Cleanup ();
  uint32_t nPackets = 0;
  if (!m_queue.empty ())
    {
      PacketQueueI it;
      for (it = m_queue.begin (); it != m_queue.end (); it++)
        {
          if (GetAddressForPacket (type, it) == addr)
            {
              if (it->hdr.IsQosData () && it->hdr.GetQosTid () == tid)
                {
                  nPackets++;
                }
            }
        }
    }
  return nPackets;
}

Ptr<const Packet>
WifiMacQueue::DequeueFirstAvailable (WifiMacHeader *hdr, Time &timestamp,
                                     const QosBlockedDestinations *blockedPackets)
{
  Cleanup ();
  Ptr<const Packet> packet = 0;
  for (PacketQueueI it = m_queue.begin (); it != m_queue.end (); it++)
    {
      if (!it->hdr.IsQosData ()
          || !blockedPackets->IsBlocked (it->hdr.GetAddr1 (), it->hdr.GetQosTid ()))
        {
          *hdr = it->hdr;
          timestamp = it->tstamp;
          packet = it->packet;
          m_queue.erase (it);
          m_size--;
          return packet;
        }
    }
  return packet;
}

Ptr<const Packet>
WifiMacQueue::PeekFirstAvailable (WifiMacHeader *hdr, Time &timestamp,
                                  const QosBlockedDestinations *blockedPackets)
{
  Cleanup ();
  for (PacketQueueI it = m_queue.begin (); it != m_queue.end (); it++)
    {
      if (!it->hdr.IsQosData ()
          || !blockedPackets->IsBlocked (it->hdr.GetAddr1 (), it->hdr.GetQosTid ()))
        {
          *hdr = it->hdr;
          timestamp = it->tstamp;
          return it->packet;
        }
    }
  return 0;
}
Ptr<WeightedFairQueue>
WifiMacQueue::GetWfq()
{
	return m_wfq;
}

} // namespace ns3
